//----------------------------------------------------------------------------------------------------------------------
// Copyright (c) 2012 James Whitworth
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//----------------------------------------------------------------------------------------------------------------------
#include <gmock/gmock.h>

#include <sqbind/sqbBind.h>

#include "fixtures/SquirrelFixture.h"
#include "TypeHelpers.h"
//----------------------------------------------------------------------------------------------------------------------

//----------------------------------------------------------------------------------------------------------------------
class BaseClass
{
};

SQBIND_DECLARE_TYPEINFO(BaseClass, BaseClass);

//----------------------------------------------------------------------------------------------------------------------
class DerivedClass : public BaseClass
{
};

SQBIND_DECLARE_TYPEINFO(DerivedClass, DerivedClass);

typedef SquirrelFixture BindClassTest;

// the comma in the template arguments cannot work in a macro so these wrapper functions are required

//----------------------------------------------------------------------------------------------------------------------
bool BindNoBaseClass(HSQUIRRELVM vm, SQInteger objectIndex, const SQChar *className)
{
  return sqb::Bind::BindClass<sqb::NoBaseClass, sqb::NoBaseClass>(vm, objectIndex, className);
}

//----------------------------------------------------------------------------------------------------------------------
bool BindBaseClass(HSQUIRRELVM vm, SQInteger objectIndex, const SQChar *className)
{
  return sqb::Bind::BindClass<BaseClass, sqb::NoBaseClass>(vm, objectIndex, className);
}

//----------------------------------------------------------------------------------------------------------------------
bool BindDerivedClass(HSQUIRRELVM vm, SQInteger objectIndex, const SQChar *className)
{
  return sqb::Bind::BindClass<DerivedClass, BaseClass>(vm, objectIndex, className);
}

//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindClassTest, TestInvalidArguments)
{
  EXPECT_FALSE(BindBaseClass(m_vm, -1, _SC("BaseClass")));

  sq_pushroottable(m_vm);

  EXPECT_FALSE(BindBaseClass(m_vm, -1, nullptr));
  EXPECT_FALSE(BindBaseClass(m_vm, -1, _SC("")));

  // ensure you can't accidentally bind the special sqb::NoBaseClass
  //
  EXPECT_FALSE(BindNoBaseClass(m_vm, -1, _SC("NoBaseClass")));

  sq_poptop(m_vm);
}

//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindClassTest, TestBindBaseClass)
{
  sq_pushroottable(m_vm);

  EXPECT_TRUE(BindBaseClass(m_vm, -1, _SC("BaseClass")));
  EXPECT_STREQ(_SC("BaseClass"), CompileAndCallReturnResult<const SQChar*>(_SC("local instance = BaseClass(); return typeof(instance)")));

  sq_poptop(m_vm);
}

//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindClassTest, TestBindDerivedClass)
{
  sq_pushroottable(m_vm);

  // should fail if base hasn't been bound
  //
  EXPECT_FALSE(BindDerivedClass(m_vm, -1, _SC("DerivedClass")));

  EXPECT_TRUE(BindBaseClass(m_vm, 1, _SC("BaseClass")));
  EXPECT_TRUE(BindDerivedClass(m_vm, -1, _SC("DerivedClass")));

  EXPECT_TRUE(CompileAndCallReturnResult<bool>(_SC("return DerivedClass.getbase() == BaseClass")));
  EXPECT_STREQ(_SC("DerivedClass"), CompileAndCallReturnResult<const SQChar*>(_SC("local instance = DerivedClass(); return typeof(instance)")));

  sq_poptop(m_vm);
}

//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindClassTest, TestBindNestedClass)
{
  sq_pushroottable(m_vm);

  EXPECT_TRUE(BindBaseClass(m_vm, -1, _SC("BaseClass")));

  sq_pushstring(m_vm, _SC("BaseClass"), -1);
  EXPECT_SQ_SUCCEEDED(m_vm, sq_rawget(m_vm, -2));
  EXPECT_EQ(OT_CLASS, sq_gettype(m_vm, -1));

  EXPECT_TRUE(BindBaseClass(m_vm, -1, _SC("NestedClass")));

  // typeof returns the name that was bound with the sqb::TypeInfo declaration not the one passed to BindClass.
  //
  EXPECT_STREQ(_SC("BaseClass"), CompileAndCallReturnResult<const SQChar*>(_SC("local instance = BaseClass.NestedClass(); return typeof(instance)")));

  sq_pop(m_vm, 2);
}
